# UpdateManager.py
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
import os
import json
import subprocess
import dbus,time,sys
import logging
from .Core.errors import *
from .Core.enums import *
from .Core.loop import mainloop
from gettext import gettext as _
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
from .Core.Database import Sqlite3Server
from .backend.BackendOstreeNext import UpdateBackend,DownloadBackend,DeployBackend,RollbackBackend,LoadCacheBackend
from .UpdateManagerDbus import UpdateManagerDbusController,UPDATER_DBUS_INTERFACE,UPDATER_DBUS_PATH,UPDATER_DBUS_SERVICE
from SystemUpdater.Core.UpdaterConfigParser import UpgradeConfig
from .Core.MyCache import MyCache
from .UpdatesAvailable import UpdatesAvailable

OUTPUT_JSON_PATH = '/var/cache/kylin-system-updater/json/'
SYSTEM_UPDATE_GROUPS = "kylin-update-desktop-system"

class UpdateManager():
    def __init__(self,options):
        try:
            self.options = options
            self._check_conf_dir()
            self.cache = MyCache()
            self.trans = UpdatesAvailable()
            self.sqlite3_server = Sqlite3Server(self)
            self.configs_uncover = UpgradeConfig(defaults_conf="/etc/kylin-system-updater/system-updater-defaults.conf")
            self.bus = dbus.SystemBus()
            self.dbus_send = self._setup_dbus(self.bus)

            cache_backend = LoadCacheBackend(self,self.options.sysroot,self.options.os)
            cache_backend.run(self.cache)
        except Exception as e:
            logging.error(e)

    def run(self):
        """Start the daemon and listen for calls."""
        logging.info("Waiting for calls...")
        try:
            mainloop.run()
        except KeyboardInterrupt:
            self.dbus_send.Quit(None)

    def start_cancel(self):
        if self.trans:
            self.trans.cancellable.cancel()

    #进行更新的操作
    def start_update(self):
        try:
            def _download_info(parent,fetched,requested,bytes_transferred,bytes_sec):
                self.dbus_send.UpdateDownloadInfo(fetched,requested,bytes_transferred,10000,bytes_sec)

            def _update_finished(parent,trans):
                if parent.exit:
                    # 检查是否可以升级
                    if trans.from_revision != trans.new_revision:
                        logging.info("Update available refs:%s old-revision:%s to new-revision:%s ",trans.available_refs,trans.from_revision,trans.new_revision)
                        self._make_meta_for_panel(trans.available_metadata,OUTPUT_JSON_PATH)
                        self.dbus_send.UpdateDetectFinishedNext(True,[SYSTEM_UPDATE_GROUPS],'')
                    else:
                        logging.info("System is up-to-date refs:%s revision:%s",trans.available_refs,trans.new_revision)
                        self.dbus_send.UpdateDetectFinishedNext(parent.exit,[],'')
                else:
                    self.dbus_send.UpdateDetectFinishedNext(parent.exit,[""],parent.error_code)

            update_backend = UpdateBackend(self,self.options.sysroot,self.options.os)
            update_backend.connect("transaction-done", _update_finished)
            update_backend.connect("download-info", _download_info)
            update_backend.run(self.trans)
        except Exception as e:
            logging.error(e)

    def start_download(self):
        try:
            def _download_info(parent,fetched,requested,bytes_transferred,bytes_sec):
                self.dbus_send.UpdateDownloadInfo(fetched,requested,bytes_transferred,10000,bytes_sec)

            def _download_finished(parent,trans):
                self.dbus_send.UpdateDownloadFinishedNext(parent.exit,[SYSTEM_UPDATE_GROUPS],parent.error_code)

                if parent.exit:
                    trans.deployable = True
                else:
                    trans.deployable = False

            download_backend = DownloadBackend(self,self.options.sysroot,self.options.os)
            download_backend.connect("transaction-done", _download_finished)
            download_backend.connect("download-info", _download_info)
            download_backend.run(self.trans)
        except Exception as e:
            logging.error(e)

    def start_deploy(self,device_status):
        try:
            def _deploy_finished(parent,trans):
                self.dbus_send.DeployUpdatFinishedNext(parent.exit,[SYSTEM_UPDATE_GROUPS],parent.error_code)

                trans.deployable = False
                if parent.exit:
                    self.configs_uncover.setValue("statusForFinishPrompt","finished_status",INSTALL_SUCCESS_FLAG)
                else:
                    self.configs_uncover.setValue("statusForFinishPrompt","finished_status",parent.error_code)
                
                if parent.exit and self.options.sysroot == None:
                    self._system_reboot_shutdown(device_status,self.options.prohibit_shutdown)

            deploy_backend = DeployBackend(self,self.options.sysroot,self.options.os)
            deploy_backend.connect("transaction-done", _deploy_finished)
            deploy_backend.run(self.trans)
        except Exception as e:
            logging.error(e)

    def start_rollback(self):
        try:
            def _rollback_finished(parent,trans):
                self.dbus_send.RollbackSysFinishedNext(parent.exit,trans.available_refs,parent.error_code)

                if parent.exit and self.options.sysroot == None:
                    self._system_reboot_shutdown("reboot",self.options.prohibit_shutdown)

            rollback_backend = RollbackBackend(self,self.options.sysroot,self.options.os)
            rollback_backend.connect("transaction-done", _rollback_finished)
            rollback_backend.run(self.trans)
        except Exception as e:
            logging.error(e)

    def start_deb_install(self, deb_path = "", _check_local_dep = False, _auto_satisfy = False, source = '', sender=None):
        try:
            kareiInterface = dbus.Interface(dbus.SystemBus().get_object('org.kylin.kare', '/org/kylin/kare'),
                                            dbus_interface='org.kylin.kare')
            logging.info("kare install:%s", deb_path)
            kareRet = kareiInterface.install ("openkylin2.0", deb_path)

            if kareRet:
                logging.info("kare install call success:%s", deb_path)
                self.sqlite3_server.transaction_manager.start_transaction(deb_path, 30)
            else:
                self.dbus_send.InstalldebFinished(False,'kare install call error','')
        except Exception as e:
            logging.error("kare install error:%s", str(e))
            self.dbus_send.InstalldebFinished(False, str(e), 'kare install call error')

    def start_purge_pkgs(self,pkgs_list,sender=None):
        try:
            kareiInterface = dbus.Interface(dbus.SystemBus().get_object('org.kylin.kare', '/org/kylin/kare'),
                                            dbus_interface='org.kylin.kare')
            for pl in pkgs_list:
                logging.info("kare remove: %s", pl)
                kareRet = kareiInterface.remove(pl)

                if kareRet:
                    logging.info("kare remove call success:%s", pl)
                    self.sqlite3_server.transaction_manager.start_transaction(pl, 10)
                else:
                    self.dbus_send.PurgePackagesFinished (False,'kare remove call error','')
        except Exception as e:
            logging.error("kare remove error:%s", str(e))
            self.dbus_send.PurgePackagesFinished (False,'kare remove error','')

    def _make_meta_for_panel(self,data,output_path):
        groups_base_info = {}
        output_json = {}
        try: 
            if not os.path.exists(output_path):
                os.makedirs(output_path)
                logging.info('making the ConfigPath(%s) is complete...',output_path)

            output_config_name = output_path + SYSTEM_UPDATE_GROUPS + '.json'
            groups_base_info.update({"version":data.setdefault("version","")})
            groups_base_info.update({"update-type":data.setdefault("update-type",SYSTEM_UPDATE_GROUPS)})

            update_name = {"zh_CN":data.setdefault("update-name_zh_CN","系统更新"),"en_US":data.setdefault("update-name_en_US","System update")}

            groups_base_info.update({"update-name":update_name})
            groups_base_info.update({"description":data.setdefault("description","")})
            groups_base_info.update({"changelog":data.setdefault("changelog","")})
            groups_base_info.update({"total_download_size":data.setdefault("total_download_size","")})
            groups_base_info.update({"total_install_size":data.setdefault("total_install_size","")})
            groups_base_info.update({"icon":data.setdefault("icon","/usr/share/upgrade/icon/kylin-update-desktop-system.png")})

            output_json.update(groups_base_info)

            with open(output_config_name, 'w', encoding='utf-8') as f:
                json.dump(output_json, f, ensure_ascii=False, indent=4)                   
            logging.info("Generate Jsonfile(%s) to complete... ",output_config_name)
        except Exception as e:
            logging.error(e)

    def _check_conf_dir(self):
        # 检查var配置目录是否存在 不存在时进行创建
        VAR_CONFG_DIR = '/var/lib/kylin-system-updater'
        if not os.path.exists(VAR_CONFG_DIR):
            os.makedirs(VAR_CONFG_DIR)

    def _system_reboot_shutdown(self,status="reboot",prohibit_shutdown=False):
        if status == "shutdown":
            args = ["/sbin/shutdown","-h","now"]
        else:
            args = ["/sbin/shutdown","-r","now"]

        if prohibit_shutdown:
            pass
        else:
            logging.info("The device will be %s by run system command(%r)...",status,args)
            p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True)
            if p.returncode != 0:
                logging.info("The device has (%s) Failed...",status)
                return False
    
    def _setup_dbus(self,bus):
        # check if there is another g-a-i already and if not setup one
        # listening on dbus
        try:
            bus_name = dbus.service.BusName(UPDATER_DBUS_SERVICE,
                                            bus,do_not_queue=True)
            logging.info("Initiate dbus success ...")
            return  UpdateManagerDbusController(self,bus,bus_name)
        except dbus.exceptions.NameExistsException:
            if self.options.replace is False:
                logging.critical("Another daemon is already running")
                sys.exit(1)
            logging.warning("Replacing already running daemon")
            
            retry_reboot_times = 0
            the_other_guy = bus.get_object(UPDATER_DBUS_SERVICE,
                                            UPDATER_DBUS_PATH)
            the_other_guy.Quit(dbus_interface=UPDATER_DBUS_INTERFACE,
                                timeout=300)
            time.sleep(1)
            while True:
                retry_reboot_times = retry_reboot_times + 1
                #当重试次数超过5次时退出程序
                if retry_reboot_times > 5:
                    logging.critical("Reboot backend is Failed...")
                    sys.exit(1)
                try:
                    bus_name = dbus.service.BusName(UPDATER_DBUS_SERVICE,
                                                    bus,
                                                    do_not_queue=True)
                    logging.warning("Replacing already running daemon to Success...")
                    return  UpdateManagerDbusController(self,bus,bus_name)
                except dbus.exceptions.NameExistsException:
                    the_other_guy = bus.get_object(UPDATER_DBUS_SERVICE,
                                                    UPDATER_DBUS_PATH)
                    the_other_guy.Quit(dbus_interface=UPDATER_DBUS_INTERFACE,
                                        timeout=300)
                    logging.warning("Dbus has not withdrawn and retry reboot times:%d...",retry_reboot_times)
                    time.sleep(1)